/*
 *  Copyright 2012 Anton Van Zyl. http://code.google.com/p/java-swiss-knife/
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package com.knife.web.browser.detection;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;

import com.knife.web.browser.Platform;
import com.knife.web.browser.UserAgent;

/**
 * A mechanism to detect client software, version and platform from a user-agent
 * string. This class is written as a singleton and a call on
 * WebClient.detect(input) will return a instance of the class. The default
 * constructor is left public to enable the use of the class in comparing. <br/>
 * 
 * <br/>
 * Please visit <a
 * href="http://code.google.com/p/java-swiss-knife/">Java-Swiss-Knife</a> and
 * comment, rate, contribute or raise a issue/enhancement for my library. <br/>
 * 
 * @author Anton Van Zyl
 * 
 */
public final class WebClient {

	private final UserAgent userAgent;
	private final int majorVersion;
	private final String fullVersion;
	private final Platform platform;
	private final boolean AppleWebKit;

	public WebClient(UserAgent userAgent, int majorVersion, String fullVersion, Platform platform, boolean hasAppleWebKit) {
		this.userAgent = userAgent;
		this.majorVersion = majorVersion;
		this.fullVersion = fullVersion;
		this.platform = platform;
		this.AppleWebKit = hasAppleWebKit;
	}

	public Platform getPlatform() {
		return platform;
	}

	public String getFullVersion() {
		return fullVersion;
	}

	public int getMajorVersion() {
		return majorVersion;
	}

	public UserAgent getUserAgent() {
		return userAgent;
	}

	public boolean isAppleWebKit() {
		return AppleWebKit;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final WebClient other = (WebClient) obj;
		if (this.userAgent != other.userAgent) {
			return false;
		}
		if (this.majorVersion != other.majorVersion) {
			return false;
		}
		if ((this.fullVersion == null) ? (other.fullVersion != null) : !this.fullVersion.equals(other.fullVersion)) {
			return false;
		}
		if (this.platform != other.platform) {
			return false;
		}
		if (this.AppleWebKit != other.AppleWebKit) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		int hash = 7;
		hash = 37 * hash + (this.userAgent != null ? this.userAgent.hashCode() : 0);
		hash = 37 * hash + this.majorVersion;
		hash = 37 * hash + (this.fullVersion != null ? this.fullVersion.hashCode() : 0);
		hash = 37 * hash + (this.platform != null ? this.platform.hashCode() : 0);
		return hash;
	}

	private boolean checkInlineSupport(boolean returnMhtml) {
		boolean mhtml = false;
		boolean dataUri = false;
		switch (getUserAgent()) {
		case CHROME:
		case FIREFOX:
		case SAFARI:
			dataUri = true;
			break;
		case OPERA:
			if (getMajorVersion() >= 7) {
				dataUri = true;
			}
			break;
		case IE:
			if (getMajorVersion() >= 8) {
				dataUri = true;
			} else if (getMajorVersion() >= 6 && !platform.equals(Platform.WINVISTA)) { // vista
																						// is
																						// unreliable
																						// for
																						// MHTML
				mhtml = true;
			}
			break;
		}
		return (returnMhtml ? mhtml : dataUri);
	}

	public boolean supportsDataUris() {
		return checkInlineSupport(false);
	}

	public boolean supportsMHTML() {
		return checkInlineSupport(true);
	}

	public static WebClient detect(HttpServletRequest req) {
		return detect(req.getHeader("User-Agent"));
	}

	// Note!!!! the order of the if statements if vital to the correct detection
	// of the platform!!!
	private static Platform detectedPlatform(String userAgent) {
		if (StringUtils.isEmpty(userAgent)) {
			return Platform.UNKNOWN;
		} else if (userAgent.contains("Android")) {
			return Platform.ANDROID;
		} else if (userAgent.contains("BlackBerry")) {
			return Platform.BLACKBERRY;
		} else if (userAgent.contains("HTC")) {
			return Platform.HTC;
		} else if (userAgent.contains("SAMSUNG")) {
			return Platform.SAMSUNG;
		} else if (userAgent.contains("SonyEricsson")) {
			return Platform.SONYERICSSON;
		} else if (userAgent.contains("Symbian") || userAgent.contains("SymbOS")) {
			return Platform.SYMBIAN;
		} else if (userAgent.contains("Nokia") || userAgent.contains("Series")) {
			return Platform.NOKIA;
		} else if (userAgent.contains("J2ME")) {
			return Platform.JAVA_ME;
		} else if (userAgent.contains("iPhone") || userAgent.contains("iPod") || userAgent.contains("iPad")) {
			return Platform.IOS;
		} else if (userAgent.contains("Mac OS X")) {
			return Platform.MACOSX;
		} else if (userAgent.contains("Windows NT 5.0")) {
			return Platform.WIN2K;
		} else if (userAgent.contains("Windows NT 5.1") || userAgent.contains("Windows NT 5.2") || userAgent.contains("Windows XP")) {
			return Platform.WINXP;
		} else if (userAgent.contains("Windows NT 6.0")) {
			return Platform.WINVISTA;
		} else if (userAgent.contains("Windows NT 6.1")) {
			return Platform.WIN7;
		} else if (userAgent.contains("Windows NT")) {
			return Platform.WINNT;
		} else if (userAgent.contains("Windows CE") || userAgent.contains("Windows Phone OS")) {
			return Platform.WINMobile;
		} else if (userAgent.contains("Linux")) {
			return Platform.LINUX;
		}
		return Platform.UNKNOWN;
	}

	// Note!!!! the order of the if statements if vital to the correct detection
	// of the UserAgent!!!
	/**
	 * This will take the userAgent and try and determine the present browser,
	 * version, and more from the input.<br/>
	 * I try and cover the most used browsers and platforms and will update if I
	 * find a unknown browser in one of my. If a browser or platform is missing,
	 * raise a issue on <a
	 * href="http://code.google.com/p/java-swiss-knife/">Java-Swiss-Knife</a>
	 * 
	 * @param userAgentString
	 *            - This is the standard in a browser header and can be found in
	 *            the <code>HttpServletRequest</code> object.
	 * @return <code>WebClient</code> object
	 */
	public static WebClient detect(String userAgentString) {
		UserAgent ua = UserAgent.UNKNOWN;
		int major = 0;
		String ver = "<unknown>";

		if (!StringUtils.isEmpty(userAgentString)) {
			if (userAgentString.contains("Yahoo! Slurp")) {
				ua = UserAgent.YAHOO_SLURP;
			} else if (userAgentString.contains("Googlebot/")) {
				ua = UserAgent.GOOGLEBOT;
				ver = getFullVersion(userAgentString, "Googlebot/", ';');
			} else if (userAgentString.contains("msnbot/")) {
				ua = UserAgent.MSNBOT;
				ver = getFullVersion(userAgentString, "msnbot/", ' ');
			} else if (userAgentString.contains("Chrome/")) {
				ua = UserAgent.CHROME;
				ver = getFullVersion(userAgentString, "Chrome/", ' ');
			} else if (userAgentString.contains("Safari/") && userAgentString.contains("Mobile")) {
				ua = UserAgent.MOBILE_SAFARI;
				ver = getFullVersion(userAgentString, "Version/", ' ');
			} else if (userAgentString.contains("Safari/")) {
				ua = UserAgent.SAFARI;
				ver = getFullVersion(userAgentString, "Version/", ' ');
			} else if (userAgentString.contains("Opera Mini/")) {
				ua = UserAgent.OPERA_MINI;
				ver = getFullVersion(userAgentString, "Opera Mini/", '/');
			} else if (userAgentString.contains("Opera ")) {
				ua = UserAgent.OPERA;
				ver = getFullVersion(userAgentString, "Opera ", ' ');
			} else if (userAgentString.contains("Firefox/")) {
				ua = UserAgent.FIREFOX;
				ver = getFullVersion(userAgentString, "Firefox/", ' ');
			} else if (userAgentString.contains("IEMobile")) {
				ua = UserAgent.IEMOBILE;
				ver = getFullVersion(userAgentString, "IEMobile ", ';');
				if (StringUtils.isEmpty(ver)) {
					ver = getFullVersion(userAgentString, "IEMobile/", ';');
				}
			} else if (userAgentString.contains("MSIE ")) {
				ua = UserAgent.IE;
				ver = getFullVersion(userAgentString, "MSIE ", ';');
			} else if (userAgentString.contains("Opera/")) {
				ua = UserAgent.OPERA;
				ver = getFullVersion(userAgentString, "Opera/", ' ');
			}

			try {
				major = safeInteger(ver);
			} catch (NumberFormatException nfe) {
				// ver = null;
				major = 0;
			}

		}
		Platform platform = detectedPlatform(userAgentString);
		boolean hasAppleWebKit = hasAppleWebKit(userAgentString);

		return new WebClient(ua, major, ver, platform, hasAppleWebKit);
	}

	private static boolean hasAppleWebKit(String userAgentString) {
		if (userAgentString.contains("AppleWebKit")) {
			return true;
		}
		return false;
	}

	@Override
	public String toString() {
		return userAgent + " " + fullVersion + " / " + platform;
	}

	private static String getFullVersion(String userAgent, String userAgentId, char delim) {

		String ver = userAgent.substring(userAgent.indexOf(userAgentId) + userAgentId.length());

		int end = ver.indexOf(delim);
		if (end < 0) {
			end = ver.length();
		}
		ver = ver.substring(0, end).trim();

		return ver;
	}

	private static int safeInteger(String ver) {

		if (ver != null) {
			int end = ver.indexOf('.');
			if (end > 0) {
				ver = ver.substring(0, end);
			}
			return Integer.parseInt(ver);
		}
		return 0;
	}
}
